Add container summary on card face, fix df parsing for wrapped lines, collect OS info, faster startup and refresh
All checks were successful
Build-Publish / build (linux/amd64) (push) Successful in 5s
Build-Publish / build (linux/arm64) (push) Successful in 12s
Build-Publish / create-manifest (push) Successful in 2s
Build-Publish / publish-template (push) Successful in 15s

This commit is contained in:
j
2026-03-08 16:09:46 +13:00
parent 8a1ec98fbd
commit 4353afafae
5 changed files with 100 additions and 19 deletions

View File

@@ -249,7 +249,7 @@ def collect_all():
def collector_loop(): def collector_loop():
time.sleep(10) # Let the app start up time.sleep(2) # Brief pause to let Flask start
while True: while True:
try: try:
collect_all() collect_all()

View File

@@ -186,13 +186,18 @@ gather_container_stats() {
echo "mem_percent=$mem_pct" echo "mem_percent=$mem_pct"
fi fi
# Disk (rootfs) # Disk (rootfs) - merge wrapped lines, find the / mount
local disk_info local disk_info
disk_info=$($exec_cmd df -B1 / 2>/dev/null | tail -1) disk_info=$($exec_cmd df -B1 / 2>/dev/null | awk 'NR>1{line=line $0" "} END{print line}')
if [ -n "$disk_info" ]; then if [ -n "$disk_info" ]; then
echo "disk_total=$(echo "$disk_info" | awk '{print $2}')" # Extract numbers - find the fields: total used avail percent
echo "disk_used=$(echo "$disk_info" | awk '{print $3}')" local d_total d_used d_pct
echo "disk_percent=$(echo "$disk_info" | awk '{gsub(/%/,""); print $5}')" d_total=$(echo "$disk_info" | awk '{for(i=1;i<=NF;i++) if($i+0>0 && $i !~ /%/){print $i; exit}}')
d_used=$(echo "$disk_info" | awk '{for(i=1;i<=NF;i++) if($i+0>0 && $i !~ /%/){n++; if(n==2){print $i; exit}}}')
d_pct=$(echo "$disk_info" | grep -oE '[0-9]+%' | head -1 | tr -d '%')
[ -n "$d_total" ] && echo "disk_total=$d_total"
[ -n "$d_used" ] && echo "disk_used=$d_used"
[ -n "$d_pct" ] && echo "disk_percent=$d_pct"
fi fi
# IP # IP
@@ -200,6 +205,11 @@ gather_container_stats() {
ip=$($exec_cmd hostname -I 2>/dev/null | awk '{print $1}') ip=$($exec_cmd hostname -I 2>/dev/null | awk '{print $1}')
[ -n "$ip" ] && echo "ip=$ip" [ -n "$ip" ] && echo "ip=$ip"
# OS
local os_pretty
os_pretty=$($exec_cmd cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d '"')
[ -n "$os_pretty" ] && echo "os=$os_pretty"
# Uptime # Uptime
local uptime_s local uptime_s
uptime_s=$($exec_cmd cut -d' ' -f1 /proc/uptime 2>/dev/null | cut -d. -f1) uptime_s=$($exec_cmd cut -d' ' -f1 /proc/uptime 2>/dev/null | cut -d. -f1)
@@ -217,8 +227,11 @@ _sudo() {
# Proxmox LXC (pct) # Proxmox LXC (pct)
if command -v pct &>/dev/null; then if command -v pct &>/dev/null; then
_sudo pct list 2>/dev/null | tail -n +2 | while read -r vmid status _ name _; do _sudo pct list 2>/dev/null | tail -n +2 | while read -r line; do
[ -z "$vmid" ] && continue [ -z "$line" ] && continue
vmid=$(echo "$line" | awk '{print $1}')
status=$(echo "$line" | awk '{print $2}')
name=$(echo "$line" | awk '{print $NF}')
echo "[container:pct-${vmid}]" echo "[container:pct-${vmid}]"
echo "type=lxc" echo "type=lxc"
echo "platform=proxmox" echo "platform=proxmox"

View File

@@ -327,6 +327,63 @@ main {
color: #475569; color: #475569;
} }
/* --- Container Summary (on card face) --- */
.ct-summary-list {
margin-top: 10px;
padding-top: 8px;
border-top: 1px solid #334155;
display: flex;
flex-direction: column;
gap: 3px;
}
.ct-summary-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.7rem;
color: #94a3b8;
overflow: hidden;
}
.status-dot-sm {
width: 5px;
height: 5px;
border-radius: 50%;
flex-shrink: 0;
}
.status-dot-sm.online {
background: #22c55e;
}
.status-dot-sm.offline {
background: #ef4444;
}
.ct-summary-name {
font-weight: 600;
color: #cbd5e1;
white-space: nowrap;
}
.ct-summary-os {
color: #64748b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ct-summary-ip {
color: #64748b;
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
font-size: 0.65rem;
white-space: nowrap;
margin-left: auto;
flex-shrink: 0;
}
/* --- Container / VM Sub-cards --- */ /* --- Container / VM Sub-cards --- */
.container-grid { .container-grid {

View File

@@ -63,6 +63,8 @@
</div> </div>
{% endif %} {% endif %}
{% set containers = d.get('container', []) if d.get('container') else [] %}
{% if server.is_online %} {% if server.is_online %}
<div class="usage-bars"> <div class="usage-bars">
<div class="usage-row"> <div class="usage-row">
@@ -96,6 +98,20 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if containers %}
<div class="ct-summary-list">
{% for ct in containers %}
{% set ct_up = ct.get('status', '')|lower in ['running', 'started'] %}
<div class="ct-summary-item">
<span class="status-dot-sm {% if ct_up %}online{% else %}offline{% endif %}"></span>
<span class="ct-summary-name">{{ ct.get('name', ct.get('id', '?')) }}</span>
{% if ct.get('os') %}<span class="ct-summary-os">{{ ct.get('os') }}</span>{% endif %}
{% if ct.get('ip') %}<span class="ct-summary-ip">{{ ct.get('ip') }}</span>{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% else %} {% else %}
<div class="offline-label">Unreachable</div> <div class="offline-label">Unreachable</div>
{% endif %} {% endif %}
@@ -254,7 +270,6 @@
</div> </div>
<!-- Containers / VMs --> <!-- Containers / VMs -->
{% set containers = d.get('container', []) if d.get('container') else [] %}
{% if containers %} {% if containers %}
<div class="detail-section wide"> <div class="detail-section wide">
<h4>Containers &amp; VMs</h4> <h4>Containers &amp; VMs</h4>
@@ -396,16 +411,10 @@
if (card) toggleDetails(card); if (card) toggleDetails(card);
} }
// Auto-refresh without full page reload if a card is expanded, // If no servers have data yet, refresh quickly; otherwise every 60s
// otherwise do a simple reload const hasData = document.querySelectorAll('.server-card').length > 0;
setInterval(function() { const refreshMs = hasData ? 60000 : 5000;
if (document.querySelector('.server-card.expanded')) { setInterval(function() { location.reload(); }, refreshMs);
// A card is open - reload page preserving hash
location.reload();
} else {
location.reload();
}
}, 60000);
// Restore state on load // Restore state on load
restoreExpanded(); restoreExpanded();

View File

@@ -10,6 +10,8 @@ services:
- ${SSH_KEY_PATH}:/app/ssh_key:ro - ${SSH_KEY_PATH}:/app/ssh_key:ro
- ${CONFIG_PATH}/infrastructure.conf:/app/infrastructure.conf:ro - ${CONFIG_PATH}/infrastructure.conf:/app/infrastructure.conf:ro
- app_data:/app/data - app_data:/app/data
stop_signal: SIGINT
stop_grace_period: 5s
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"] test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]