12th August 2024 / 11:15 AM
I wrote a simple web based monitoring tool with a CGI Script and used HTMX for the statistics to refresh automatically.
When looking for an admin interface to use, I noticed that every single one I was able to find, had way too much stuff going on for my use case (too many features i would never use), or they are not FOSS, which I didn't want for my server. This is when I decided that it would make sense to build my own!
So I started researching on how I could get some system statistics and a shell on a website. Then I discovered CGI scripts (Common Gateway Interface): A way to write shell commands inside HTML tags and then let it display with a webserver. This is quite an old technique, introduced, as of my research in the early 1990s, but still enough for my usecase.
The information I found on the use of CGI scripts was mainly focused on the Apache2 webserver, so I had to find a way to use them with Nginx. This took some more research and tinkering but I eventually got it working after some difficulties.
To get it working, I used fastcgi (in debian the package is called "fcgiwrap ") and some changes in my Nginx configuration (/etc/nginx/sites-available/default):
server {
listen PORT;
server_name NAME;
root /var/www/html;
index index.html;
location /cgi-bin/ {
alias /usr/lib/cgi-bin/; # Default path for cgi scripts.
fastcgi_pass unix:/var/run/fcgiwrap.socket;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib$fastcgi_script_name;
}
}
Particularly I had to add the lines in the "location" block.
Then I had to write the CGI script itself. This is what I wrote:
#!/bin/sh
echo "Content-Type: text/html"
echo ""
echo "<!DOCTYPE html>"
echo "<html lang=\"de\">"
echo "<head>"
echo " <meta charset=\"UTF-8\">"
echo " <title>BOP Admin Panel</title>"
echo " <link rel=\"icon\" type=\"image/x-icon\" href=\"/assets/favicon.png\">"
echo " <style>"
echo " body { font-family: Arial, sans-serif; color: #ffffff; background-color: #000000; }"
echo " .section { margin-bottom: 20px; }"
echo " .section h2 { margin: 0; }"
echo " pre { color: #ffffff; font-size: 14px; white-space: pre-wrap; }"
echo " </style>"
echo "</head>"
echo "<body>"
# CPU Temperature
echo "<div class='section'>"
echo "<h2>CPU Temperature:</h2>"
if command -v sensors >/dev/null 2>&1; then
echo "<pre>$(sensors | grep Core | awk '{print $1, $2, $3}')</pre>"
else
echo "<p>Command 'sensors' not available.</p>"
fi
echo "</div>"
# CPU Usage
echo "<div class='section'>"
echo "<h2>CPU Usage:</h2>"
echo "<pre>"
(
# First measurement
awk '/^cpu[0-9]+/ {print $1, $2+$3+$4+$5+$6, $5}' /proc/stat > /tmp/stat1
sleep 1
# Second measurement
awk '/^cpu[0-9]+/ {print $1, $2+$3+$4+$5+$6, $5}' /proc/stat > /tmp/stat2
# Calculation & Output
awk '
NR==FNR {
idle_before[$1]=$3
total_before[$1]=$2
next
}
/^cpu[0-9]+/ {
idle[$1]=$3
total[$1]=$2
if (length(idle_before[$1])) {
idle_diff = idle[$1] - idle_before[$1]
total_diff = total[$1] - total_before[$1]
usage = (total_diff > 0) ? (100 * (1 - (idle_diff / total_diff))) : 0
core = substr($1, 4)
printf "Core %d: %.1f%%\n", core, usage
}
}' /tmp/stat1 /tmp/stat2
)
echo "</pre>"
echo "</div>"
# RAM Usage
echo "<div class='section'>"
echo "<h2>RAM Usage:</h2>"
echo "<pre>"
echo "$(awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {usage=((total-available)/total)*100; printf "Percent: %.2f%%\n", usage}' /proc/meminfo)"
echo "$(awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {printf "MiB: %d / %d MiB\n", (total-available)/1024, total/1024}' /proc/meminfo)"
echo "</pre>"
echo "</div>"
# Uptime
echo "<div class='section'>"
echo "<h2>Uptime:</h2>"
echo "<pre>$(uptime -p | sed 's/up //')</pre>"
echo "</div>"
echo "</body>"
echo "</html>"
# Removing temporary files.
rm /tmp/stat1 /tmp/stat2
It displays the CPU Temperature, CPU Usage, RAM Usage in MiB and percentage and the uptime.
I them edited my index.html: I inserted HTMX into it, so that the statistics get refreshed once a second. To do this, I added these few lines:
<script src="https://unpkg.com/htmx.org@2.0.1"></script>
<div hx-get="/cgi-bin/script.sh" hx-swap="innerHTML" hx-trigger="every 1s">
<!-- Script Content -->
</div>
This creates a div and swaps its content to the content of the CGI-script every one second via HTMX.
Info on CSS, title, etc: I figured out, to change the title, favicon and CSS of your index.html, you might have to change it in the CGI-script instead of your HTML style tag or external stylesheet if you plan on doing something similar.
To have shell access in my small "admin panel", I installed ShellInABox and created an iframe that contains it into my HTML. This is what it looks like in the end:
23rd August 2024 / 09:15 AM
I did not really like the look of the admin panel, so I changed it: I let the statistics display next to each other and put them into boxes with borders, so that they can be differentiated from each other much easier.
I also added the disk usage to the statistics. I also planned on adding disk I/O and netin / netout statistics, but I did not figure out how to grab that info in a proper way yet.